﻿//-----------------------------------------------------------------------
// <copyright file="Set.cs" company="Rhys Parry">
//    Copyright Rhys Parry 2008
//    This file is released under the Apache 2.0 License
// </copyright>
//-----------------------------------------------------------------------
namespace Smarts.Core
{
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// A generic class to implement Sets. Sets have no guaranteed order
    /// and can not have duplicate items.
    /// </summary>
    /// <typeparam name="T">The type of items held in the set</typeparam>
    public class Set<T> : IEnumerable<T>
    {
        /// <summary>
        /// The items in the set
        /// </summary>
        private readonly HashSet<T> _items;

        /// <summary>
        /// Initializes a new instance of the <see cref="Set&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="someCollection">Some collection.</param>
        public Set(IEnumerable<T> someCollection)
        {
            _items = new HashSet<T>();
            foreach (var item in someCollection)
            {
                this[item] = true;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Set&lt;T&gt;"/> class.
        /// </summary>
        public Set()
        {
            _items = new HashSet<T>();
        }

        /// <summary>
        /// Gets the Empty set (i.e. the set with no elements)
        /// </summary>
        public static Set<T> Empty
        {
            get
            {
                return new Set<T>();
            }
        }

        /// <summary>
        /// Gets the number of items in the set
        /// </summary>
        public int Count
        {
            get
            {
                return _items.Count;
            }
        }

        /// <summary>
        /// Used to add or remove items to a set and to
        /// determine whether they exist in the set
        /// </summary>
        /// <param name="item">The item of interest</param>
        /// <returns>Whether <paramref name="item"/> exists in the set or not.</returns>
        public bool this[T item]
        {
            get
            {
                return _items.Contains(item);
            }

            set
            {
                if (value != this[item])
                {
                    if (value)
                    {
                        _items.Add(item);
                    }
                    else
                    {
                        _items.Remove(item);
                    }
                }
            }
        }

        /// <summary>
        /// The Set Union operator
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>The items that exist in <paramref name="setA"/> plus
        /// the items that exists in <paramref name="setB"/>.</returns>
        public static Set<T> operator +(Set<T> setA, Set<T> setB)
        {
            var unionSet = new Set<T>(setA);
            foreach (var item in setB)
            {
                unionSet[item] = true;
            }

            return unionSet;
        }

        /// <summary>
        /// The Set difference operator
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>The items that exist in <paramref name="setA"/> that do
        /// not exist in <paramref name="setB"/></returns>
        public static Set<T> operator -(Set<T> setA, Set<T> setB)
        {
            return new Set<T>(from item in setA
                              where !setB[item]
                              select item);
        }

        /// <summary>
        /// The Set intersection operator
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>Only the items that exist in both <paramref name="setA"/>
        /// and also exist in <paramref name="setB"/>.</returns>
        public static Set<T> operator &(Set<T> setA, Set<T> setB)
        {
            return new Set<T>(from item in setA
                              where setB[item]
                              select item);
        }

        /// <summary>
        /// Evaluates whether two sets are equal (i.e. have the same number
        /// of items and each item from <paramref name="setA"/> is in
        /// <paramref name="setB"/>
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>Whether the two sets are considered equal</returns>
        public static bool operator ==(Set<T> setA, Set<T> setB)
        {
            if (setA.Count != setB.Count)
            {
                return false;
            }

            foreach (var item in setA)
            {
                if (!setB[item])
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Evaluates whether two sets are not equal (i.e. have a different number
        /// of items or items exist in one set, but not the other.
        /// <paramref name="setB"/>
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>Whether the two sets are considered to be different</returns>
        public static bool operator !=(Set<T> setA, Set<T> setB)
        {
            if (setA.Count != setB.Count)
            {
                return true;
            }

            foreach (var item in setA)
            {
                if (!setB[item])
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// The Set Union operation
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>The items that exist in <paramref name="setA"/> plus
        /// the items that exists in <paramref name="setB"/>.</returns>
        public static Set<T> Add(Set<T> setA, Set<T> setB)
        {
            return setA + setB;
        }

        /// <summary>
        /// The Set Union operation
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>The items that exist in <paramref name="setA"/> plus
        /// the items that exists in <paramref name="setB"/>.</returns>
        public static Set<T> Union(Set<T> setA, Set<T> setB)
        {
            return setA + setB;
        }

        /// <summary>
        /// The Set difference operation
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>The items that exist in <paramref name="setA"/> that do
        /// not exist in <paramref name="setB"/></returns>
        public static Set<T> Subtract(Set<T> setA, Set<T> setB)
        {
            return setA - setB;
        }

        /// <summary>
        /// The Set difference operation
        /// </summary>
        /// <param name="setA">The first set</param>
        /// <param name="setB">The second set</param>
        /// <returns>The items that exist in <paramref name="setA"/> that do
        /// not exist in <paramref name="setB"/></returns>
        public static Set<T> Difference(Set<T> setA, Set<T> setB)
        {
            return setA - setB;
        }

        /// <summary>
        /// Determines whether the object is the same set
        /// </summary>
        /// <param name="obj">The object to compare</param>
        /// <returns>Whether the object is equivalent to the set</returns>
        public override bool Equals(object obj)
        {
            if (obj.GetType() == typeof(Set<T>))
            {
                return (Set<T>)obj == this;
            }

            return false;
        }

        /// <summary>
        /// Calls the base GetHashCode
        /// </summary>
        /// <returns>The hash code as defined by the base class</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Determines whether the specified set is a subset
        /// of the current set
        /// </summary>
        /// <param name="subset">The Set of items</param>
        /// <returns>Whether all items in <paramref name="subset"/> exist in the set</returns>
        public bool Contains(Set<T> subset)
        {
            foreach (var item in subset)
            {
                if (!this[item])
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Determines whether the specified set is a superset
        /// of the current set
        /// </summary>
        /// <param name="superset">The set of items</param>
        /// <returns>Whether all the items in the current set exist in the <paramref name="superset"/></returns>
        public bool IsSubsetOf(Set<T> superset)
        {
            return superset.Contains(this);
        }

        /// <summary>
        /// Determines whether one set intersects another
        /// </summary>
        /// <param name="otherSet">The other set</param>
        /// <returns>Whether or not any of the items in <paramref name="otherSet"/> exist in the set</returns>
        public bool Intersects(Set<T> otherSet)
        {
            foreach (var item in otherSet)
            {
                if (this[item])
                {
                    return true;
                }
            }

            return false;
        }

        #region IEnumerable<T> Members
        /// <summary>
        /// Provides an enumerator for going over the values in the set
        /// </summary>
        /// <returns>The IEnumerator for the values in the set</returns>
        public IEnumerator<T> GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members
        /// <summary>
        /// Provides an enumerator for going over the values in the set
        /// </summary>
        /// <returns>The IEnumerator for the values in the set</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        #endregion

        #region IEnumerable<T> Members
        /// <summary>
        /// Provides an enumerator for going over the values in the set
        /// </summary>
        /// <returns>The IEnumerator for the values in the set</returns>
        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        #endregion
    }
}
